/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.AccessController;
import javax.swing.event.SwingPropertyChangeSupport;
import sun.security.action.GetPropertyAction;

/**
 * This class provides default implementations for the JFC <code>Action</code>
 * interface. Standard behaviors like the get and set methods for
 * <code>Action</code> object properties (icon, text, and enabled) are defined
 * here. The developer need only subclass this abstract class and
 * define the <code>actionPerformed</code> method.
 * <p>
 * <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with
 * future Swing releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage
 * of all JavaBeans&trade;
 * has been added to the <code>java.beans</code> package.
 * Please see {@link java.beans.XMLEncoder}.
 *
 * @author Georges Saab
 * @see Action
 */
public abstract class AbstractAction implements Action, Cloneable, Serializable {

  /**
   * Whether or not actions should reconfigure all properties on null.
   */
  private static Boolean RECONFIGURE_ON_NULL;

  /**
   * Specifies whether action is enabled; the default is true.
   */
  protected boolean enabled = true;


  /**
   * Contains the array of key bindings.
   */
  private transient ArrayTable arrayTable;

  /**
   * Whether or not to reconfigure all action properties from the
   * specified event.
   */
  static boolean shouldReconfigure(PropertyChangeEvent e) {
    if (e.getPropertyName() == null) {
      synchronized (AbstractAction.class) {
        if (RECONFIGURE_ON_NULL == null) {
          RECONFIGURE_ON_NULL = Boolean.valueOf(
              AccessController.doPrivileged(new GetPropertyAction(
                  "swing.actions.reconfigureOnNull", "false")));
        }
        return RECONFIGURE_ON_NULL;
      }
    }
    return false;
  }

  /**
   * Sets the enabled state of a component from an Action.
   *
   * @param c the Component to set the enabled state on
   * @param a the Action to set the enabled state from, may be null
   */
  static void setEnabledFromAction(JComponent c, Action a) {
    c.setEnabled((a != null) ? a.isEnabled() : true);
  }

  /**
   * Sets the tooltip text of a component from an Action.
   *
   * @param c the Component to set the tooltip text on
   * @param a the Action to set the tooltip text from, may be null
   */
  static void setToolTipTextFromAction(JComponent c, Action a) {
    c.setToolTipText(a != null ?
        (String) a.getValue(Action.SHORT_DESCRIPTION) : null);
  }

  static boolean hasSelectedKey(Action a) {
    return (a != null && a.getValue(Action.SELECTED_KEY) != null);
  }

  static boolean isSelected(Action a) {
    return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY));
  }


  /**
   * Creates an {@code Action}.
   */
  public AbstractAction() {
  }

  /**
   * Creates an {@code Action} with the specified name.
   *
   * @param name the name ({@code Action.NAME}) for the action; a value of {@code null} is ignored
   */
  public AbstractAction(String name) {
    putValue(Action.NAME, name);
  }

  /**
   * Creates an {@code Action} with the specified name and small icon.
   *
   * @param name the name ({@code Action.NAME}) for the action; a value of {@code null} is ignored
   * @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a value of {@code null}
   * is ignored
   */
  public AbstractAction(String name, Icon icon) {
    this(name);
    putValue(Action.SMALL_ICON, icon);
  }

  /**
   * Gets the <code>Object</code> associated with the specified key.
   *
   * @param key a string containing the specified <code>key</code>
   * @return the binding <code>Object</code> stored with this key; if there are no keys, it will
   * return <code>null</code>
   * @see Action#getValue
   */
  public Object getValue(String key) {
    if (key == "enabled") {
      return enabled;
    }
    if (arrayTable == null) {
      return null;
    }
    return arrayTable.get(key);
  }

  /**
   * Sets the <code>Value</code> associated with the specified key.
   *
   * @param key the <code>String</code> that identifies the stored object
   * @param newValue the <code>Object</code> to store using this key
   * @see Action#putValue
   */
  public void putValue(String key, Object newValue) {
    Object oldValue = null;
    if (key == "enabled") {
      // Treat putValue("enabled") the same way as a call to setEnabled.
      // If we don't do this it means the two may get out of sync, and a
      // bogus property change notification would be sent.
      //
      // To avoid dependencies between putValue & setEnabled this
      // directly changes enabled. If we instead called setEnabled
      // to change enabled, it would be possible for stack
      // overflow in the case where a developer implemented setEnabled
      // in terms of putValue.
      if (newValue == null || !(newValue instanceof Boolean)) {
        newValue = false;
      }
      oldValue = enabled;
      enabled = (Boolean) newValue;
    } else {
      if (arrayTable == null) {
        arrayTable = new ArrayTable();
      }
      if (arrayTable.containsKey(key)) {
        oldValue = arrayTable.get(key);
      }
      // Remove the entry for key if newValue is null
      // else put in the newValue for key.
      if (newValue == null) {
        arrayTable.remove(key);
      } else {
        arrayTable.put(key, newValue);
      }
    }
    firePropertyChange(key, oldValue, newValue);
  }

  /**
   * Returns true if the action is enabled.
   *
   * @return true if the action is enabled, false otherwise
   * @see Action#isEnabled
   */
  public boolean isEnabled() {
    return enabled;
  }

  /**
   * Sets whether the {@code Action} is enabled. The default is {@code true}.
   *
   * @param newValue {@code true} to enable the action, {@code false} to disable it
   * @see Action#setEnabled
   */
  public void setEnabled(boolean newValue) {
    boolean oldValue = this.enabled;

    if (oldValue != newValue) {
      this.enabled = newValue;
      firePropertyChange("enabled",
          Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
    }
  }


  /**
   * Returns an array of <code>Object</code>s which are keys for
   * which values have been set for this <code>AbstractAction</code>,
   * or <code>null</code> if no keys have values set.
   *
   * @return an array of key objects, or <code>null</code> if no keys have values set
   * @since 1.3
   */
  public Object[] getKeys() {
    if (arrayTable == null) {
      return null;
    }
    Object[] keys = new Object[arrayTable.size()];
    arrayTable.getKeys(keys);
    return keys;
  }

  /**
   * If any <code>PropertyChangeListeners</code> have been registered, the
   * <code>changeSupport</code> field describes them.
   */
  protected SwingPropertyChangeSupport changeSupport;

  /**
   * Supports reporting bound property changes.  This method can be called
   * when a bound property has changed and it will send the appropriate
   * <code>PropertyChangeEvent</code> to any registered
   * <code>PropertyChangeListeners</code>.
   */
  protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
    if (changeSupport == null ||
        (oldValue != null && newValue != null && oldValue.equals(newValue))) {
      return;
    }
    changeSupport.firePropertyChange(propertyName, oldValue, newValue);
  }


  /**
   * Adds a <code>PropertyChangeListener</code> to the listener list.
   * The listener is registered for all properties.
   * <p>
   * A <code>PropertyChangeEvent</code> will get fired in response to setting
   * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
   * or <code>setForeground</code>.
   * Note that if the current component is inheriting its foreground,
   * background, or font from its container, then no event will be
   * fired in response to a change in the inherited property.
   *
   * @param listener The <code>PropertyChangeListener</code> to be added
   * @see Action#addPropertyChangeListener
   */
  public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
    if (changeSupport == null) {
      changeSupport = new SwingPropertyChangeSupport(this);
    }
    changeSupport.addPropertyChangeListener(listener);
  }


  /**
   * Removes a <code>PropertyChangeListener</code> from the listener list.
   * This removes a <code>PropertyChangeListener</code> that was registered
   * for all properties.
   *
   * @param listener the <code>PropertyChangeListener</code> to be removed
   * @see Action#removePropertyChangeListener
   */
  public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
    if (changeSupport == null) {
      return;
    }
    changeSupport.removePropertyChangeListener(listener);
  }


  /**
   * Returns an array of all the <code>PropertyChangeListener</code>s added
   * to this AbstractAction with addPropertyChangeListener().
   *
   * @return all of the <code>PropertyChangeListener</code>s added or an empty array if no listeners
   * have been added
   * @since 1.4
   */
  public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
    if (changeSupport == null) {
      return new PropertyChangeListener[0];
    }
    return changeSupport.getPropertyChangeListeners();
  }


  /**
   * Clones the abstract action. This gives the clone
   * its own copy of the key/value list,
   * which is not handled for you by <code>Object.clone()</code>.
   **/

  protected Object clone() throws CloneNotSupportedException {
    AbstractAction newAction = (AbstractAction) super.clone();
    synchronized (this) {
      if (arrayTable != null) {
        newAction.arrayTable = (ArrayTable) arrayTable.clone();
      }
    }
    return newAction;
  }

  private void writeObject(ObjectOutputStream s) throws IOException {
    // Store the default fields
    s.defaultWriteObject();

    // And the keys
    ArrayTable.writeArrayTable(s, arrayTable);
  }

  private void readObject(ObjectInputStream s) throws ClassNotFoundException,
      IOException {
    s.defaultReadObject();
    for (int counter = s.readInt() - 1; counter >= 0; counter--) {
      putValue((String) s.readObject(), s.readObject());
    }
  }
}
